/*
 * Copyright (C) 2005-2011 Alfresco Software Limited.
 *
 * This file is part of Alfresco
 *
 * Alfresco is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Alfresco is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
 */
package de.fme.publishing.facebook;

import java.io.File;
import java.io.Serializable;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;

import org.alfresco.model.ContentModel;
import org.alfresco.repo.content.MimetypeMap;
import org.alfresco.repo.content.filestore.FileContentReader;
import org.alfresco.repo.publishing.AbstractChannelType;
import org.alfresco.repo.publishing.PublishingModel;
import org.alfresco.service.cmr.publishing.channels.Channel;
import org.alfresco.service.cmr.repository.AssociationRef;
import org.alfresco.service.cmr.repository.ContentReader;
import org.alfresco.service.cmr.repository.ContentService;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.namespace.QName;
import org.alfresco.service.namespace.QNamePattern;
import org.alfresco.util.ParameterCheck;
import org.alfresco.util.TempFileProvider;
import org.alfresco.util.collections.CollectionUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import org.springframework.social.connect.Connection;
import org.springframework.social.facebook.api.Facebook;
import org.springframework.social.oauth2.AccessGrant;
import org.springframework.social.oauth2.GrantType;
import org.springframework.social.oauth2.OAuth2Operations;
import org.springframework.social.oauth2.OAuth2Parameters;

/**
 * @author JUP
 * @since 4.0
 */
public class FacebookChannelType extends AbstractChannelType
{
	private final static Log log = LogFactory.getLog(FacebookChannelType.class);
    public final static String ID = "facebook";
    public final static String DEFAULT_REDIRECT_URI = "http://alfresco.com/stand-alone-auth-return.html";

    private final static Set<String> DEFAULT_SUPPORTED_MIME_TYPES = CollectionUtils.unmodifiableSet(
            MimetypeMap.MIMETYPE_IMAGE_GIF,
            MimetypeMap.MIMETYPE_IMAGE_JPEG,
            MimetypeMap.MIMETYPE_IMAGE_PNG);
    private Set<String> supportedMimeTypes = DEFAULT_SUPPORTED_MIME_TYPES;

    
    private FacebookPublishingHelper publishingHelper;
    private String redirectUri = DEFAULT_REDIRECT_URI;
    private ContentService contentService;

    public void setPublishingHelper(FacebookPublishingHelper facebookPublishingHelper)
    {
        this.publishingHelper = facebookPublishingHelper;
    }

    public void setRedirectUri(String redirectUri)
    {
        this.redirectUri = redirectUri;
    }
    
    public void setContentService(ContentService contentService)
    {
        this.contentService = contentService;
    }
    
    public void setSupportedMimeTypes(Set<String> mimeTypes)
    {
        supportedMimeTypes = Collections.unmodifiableSet(new TreeSet<String>(mimeTypes));
    }

    @Override
    public boolean canPublish()
    {
        return true;
    }

    @Override
    public boolean canPublishStatusUpdates()
    {
        return true;
    }

    @Override
    public boolean canUnpublish()
    {
        return true;
    }

    @Override
    public QName getChannelNodeType()
    {
        return FacebookPublishingModel.TYPE_DELIVERY_CHANNEL;
    }	

    @Override
    public String getId()
    {
        return ID;
    }

    @Override
    public void sendStatusUpdate(Channel channel, String status)
    {
        Connection<Facebook> connection = publishingHelper.getFacebookConnectionForChannel(channel.getNodeRef());
        connection.updateStatus(status);
    }
    
    @Override
    public void publish(NodeRef nodeToPublish, Map<QName, Serializable> channelProperties)
    {
		ContentReader reader = contentService.getReader(nodeToPublish, ContentModel.PROP_CONTENT);
        if (reader.exists())
        {
            File contentFile;
            boolean deleteContentFileOnCompletion = false;
            if (FileContentReader.class.isAssignableFrom(reader.getClass()))
            {
                // Grab the content straight from the content store if we can...
                contentFile = ((FileContentReader) reader).getFile();
                
            }
            else
            {
                // ...otherwise copy it to a temp file and use the copy...
                File tempDir = TempFileProvider.getLongLifeTempDir("facebook");
                contentFile = TempFileProvider.createTempFile("facebook", "", tempDir);
                reader.getContent(contentFile);
                deleteContentFileOnCompletion = true;
            }
            try
            {
                Resource resource = new FileSystemResource(contentFile);
                Connection<Facebook> connection = publishingHelper.getConnectionFromChannelProps(channelProperties);
                Facebook api = connection.getApi();

                NodeService nodeService = getNodeService();
                String name = (String) nodeService.getProperty(nodeToPublish, ContentModel.PROP_NAME);
                String message = createPostMessage(nodeToPublish, nodeService, name);
                //PUBLISHING
                String objectId = api.mediaOperations().postPhoto(resource, message);
                String url = api.mediaOperations().getPhoto(objectId).getLink();
                storePublishingInfo(nodeToPublish, nodeService, name, objectId, url);
                
            }
            finally
            {
                if (deleteContentFileOnCompletion)
                {
                    contentFile.delete();
                }
            }
        }
        
    }
    /**
     * This Method stores publishing info independently from the publishing Info Helper
     * @param nodeToPublish
     * @param nodeService
     * @param name
     * @param objectId
     * @param url
     */
    private void storePublishingInfo(NodeRef nodeToPublish, NodeService nodeService, String name, String objectId, String url) 
	{
		nodeService.addAspect(nodeToPublish, FacebookPublishingModel.ASPECT_ASSET, null);
		
		log.info("Posted image " + name + " to FACEBOOK with id " + objectId);
		nodeService.setProperty(nodeToPublish, PublishingModel.PROP_ASSET_ID, objectId);
		log.info("Message url = " + url);
		nodeService.setProperty(nodeToPublish, PublishingModel.PROP_ASSET_URL, url);
	}
    
    private String createPostMessage(NodeRef nodeToPublish, NodeService nodeService, String name)
	{
		String message;
		String defaultMessage = "Posted from Alfresco Share.";
		NodeRef pubEventNode = getNodeFromAssocs(nodeToPublish, nodeService, PublishingModel.ASSOC_LAST_PUBLISHING_EVENT);
		
		if (pubEventNode != null)
		{
			message = (String) nodeService.getProperty(pubEventNode, PublishingModel.PROP_STATUS_UPDATE_MESSAGE);
		}
		else
		{
			String title = (String) nodeService.getProperty(nodeToPublish, ContentModel.PROP_TITLE);
			if (title == null)
			{
			    title = "";
			}
			String description = (String) nodeService.getProperty(nodeToPublish, ContentModel.PROP_DESCRIPTION);
			if (description == null)
			{
			    description = "";
			}
			message = "" + title + " " + description + defaultMessage;
		}
		return message;
	}
    
    private NodeRef getNodeFromAssocs(NodeRef nodeToPublish, NodeService nodeService, QNamePattern assocType) 
	{
		List<AssociationRef> assocs = nodeService.getTargetAssocs(nodeToPublish, assocType);
		NodeRef sourceNodeRef = null;
		if (assocs.size() == 1)
		{
			sourceNodeRef = assocs.get(0).getTargetRef();
		}
		else
		{
			log.error("Could not get NodeRef from Assocs. (" + nodeToPublish + ", " + assocType + ")");
		}
		return sourceNodeRef;
	}

	@Override
    public void unpublish(NodeRef nodeToPublish, Map<QName, Serializable> channelProperties)
    {
    	
    }

    @Override
    public String getNodeUrl(NodeRef node)
    {
        return super.getNodeUrl(node);
    }

    @Override
    public AuthUrlPair getAuthorisationUrls(Channel channel, String callbackUrl)
    {
        ParameterCheck.mandatory("channel", channel);
        if (!ID.equals(channel.getChannelType().getId()))
        {
            throw new IllegalArgumentException("Invalid channel type: " + channel.getChannelType().getId());
        }

        NodeRef channelRef = channel.getNodeRef();
        StringBuilder authStateBuilder = new StringBuilder(channelRef.getStoreRef().getProtocol()).append('.').append(
                channelRef.getStoreRef().getIdentifier()).append('.').append(channelRef.getId());
        OAuth2Operations oauthOperations = publishingHelper.getConnectionFactory().getOAuthOperations();
        OAuth2Parameters params = new OAuth2Parameters(redirectUri,
                "publish_stream,offline_access,user_photos,user_videos", authStateBuilder.toString(), null);
        String authRequestUrl = oauthOperations.buildAuthorizeUrl(GrantType.IMPLICIT_GRANT, params); 
        return new AuthUrlPair(authRequestUrl, redirectUri);
    }

    @Override
    protected AuthStatus internalAcceptAuthorisation(Channel channel, Map<String, String[]> callbackHeaders,
            Map<String, String[]> callbackParams)
    {
        AuthStatus authorised = AuthStatus.UNAUTHORISED;
        
        String accessToken = null;
        if (callbackParams.containsKey("access_token"))
        {
            //We have been given the access token directly.
            accessToken = callbackParams.get("access_token")[0];
        }
        else if (callbackParams.containsKey("code"))
        {
            //We have been passed an authorisation code that needs to be exchanged for a token
            OAuth2Operations oauthOps = publishingHelper.getConnectionFactory().getOAuthOperations();
            AccessGrant grant = oauthOps.exchangeForAccess(callbackParams.get("code")[0], redirectUri, null);
            accessToken = grant.getAccessToken();
        }
        if (accessToken != null)
        {
            Map<QName,Serializable> channelProps = new HashMap<QName, Serializable>();
            channelProps.put(PublishingModel.PROP_OAUTH2_TOKEN, accessToken);
            channelProps = getEncryptor().encrypt(channelProps);
            getChannelService().updateChannel(channel, channelProps);
            authorised = AuthStatus.AUTHORISED;
        }
        return authorised;
    }
}
